package com.inepex.inechart.shared;

import java.util.ArrayList;
import java.util.TreeMap;

import com.inepex.inechart.client.Defaults;
import com.inepex.inechart.client.label.HasLegendEntries;
import com.inepex.inechart.shared.axes.Axes;
import com.inepex.inechart.shared.axes.Axis;
import com.inepex.inechart.shared.axes.AxisDirection;
import com.inepex.inechart.shared.properties.Color;
import com.inepex.inechart.shared.properties.LineProperties;
import com.inepex.inechart.shared.util.PaddingUtil;
import com.inepex.inegraphics.shared.Context;
import com.inepex.inegraphics.shared.GraphicalObjectContainer;
import com.inepex.inegraphics.shared.gobjects.Rectangle;

/**
 *
 * 
 * A base class for modules with axes and legend.
 *
 *  
 * @author Miklós Süveges / Inepex Ltd.
 *
 */
public abstract class CartesianChartModule implements ChartModule, HasLegendEntries{

	protected final Axes axes;
	protected final CartesianAssist cartesianAssist;
	protected final ArrayList<Axis> extraAxes;

	protected boolean autoScaleViewportHorizontal;
	protected boolean autoScaleViewportVertical;
	protected boolean autoCalcPadding = true;

	//padding
	protected int minTopPadding = Defaults.paddingVertical;
	protected int minLeftPadding = Defaults.paddingHorizontal;
	protected int minBottomPadding = Defaults.paddingVertical;
	protected int minRightPadding = Defaults.paddingHorizontal;

	//border and background
	protected static final int backgroundZIndex = Integer.MIN_VALUE + 100;
	protected static final int borderZIndex = backgroundZIndex + 1;
	protected LineProperties border = null;
	protected Color backgroundColor = null;

	//legend
	protected boolean displayLegendEntries = true;
	protected TreeMap<String, Color> legendEntries;

	protected boolean visible = true;


	protected CartesianChartModule(Axes axes) {
		this.axes = axes;
		cartesianAssist = new CartesianAssist(new double[]{minTopPadding, minRightPadding, minBottomPadding, minLeftPadding});
		autoScaleViewportHorizontal = true;
		autoScaleViewportVertical = true;
		// default axes
		Axis axis = new Axis();
		axis.setAxisDirection(AxisDirection.Horizontal_Ascending_To_Right);
		setXAxis(axis);
		axis = new Axis();
		axis.setAxisDirection(AxisDirection.Vertical_Ascending_To_Top);
		setYAxis(axis);

		extraAxes = new ArrayList<Axis>();
		border = null;
	}


	/**
	 * a module should override this method to calculate and set its axes before the update() call
	 */
	public void preUpdateModule(){
		alignExtraAxes();
	}

	public GraphicalObjectContainer createBorderAndBackground(){
		GraphicalObjectContainer graphicalObjectContainer = new GraphicalObjectContainer();
		//border
		if(border != null){
			graphicalObjectContainer.add(
					new Rectangle(
							cartesianAssist.getLeftPadding() //- border.getLineWidth()
							,cartesianAssist.getTopPadding() //- border.getLineWidth()
							,cartesianAssist.getWidth()// + 2*border.getLineWidth()
							,cartesianAssist.getHeight()//+ 2*border.getLineWidth()
							,0, 
							true,
							false,
							new Context(border.getLineColor().getAlpha(),
									border.getLineColor().getColor(),
									border.getLineWidth(),
									Defaults.colorString,
									0,0,0,
									Defaults.colorString), borderZIndex));
		}
		if(backgroundColor != null){
			graphicalObjectContainer.add(
					new Rectangle(cartesianAssist.getLeftPadding()
							,cartesianAssist.getTopPadding(), cartesianAssist.getWidth(), cartesianAssist.getHeight(),
							0, 
							false,
							true,
							new Context(backgroundColor.getAlpha(),
									Defaults.colorString,
									0,
									backgroundColor.getColor(),
									0,0,0,
									Defaults.colorString), backgroundZIndex));
		}

		return graphicalObjectContainer;
	}

	public double[] getPaddingForAxes(){
		double[] padding = new double[]{minTopPadding,minRightPadding,minBottomPadding,minLeftPadding};
		if(cartesianAssist.xAxis.isVisible()) {
			padding = PaddingUtil.mergePaddings(padding, axes.getActualModulPaddingForAxis(cartesianAssist.xAxis));
		}
		if(cartesianAssist.yAxis.isVisible()) {
			padding = PaddingUtil.mergePaddings(padding, axes.getActualModulPaddingForAxis(cartesianAssist.yAxis));
		}
		for(Axis axis : extraAxes){
			if(axis.isVisible())
				padding = PaddingUtil.mergePaddings(padding, axes.getActualModulPaddingForAxis(axis));
		}
		return padding;
	}

	public void setXAxis(Axis xAxis) {
		if(cartesianAssist.xAxis != null){
			axes.removeAxis(cartesianAssist.xAxis);
		}
		cartesianAssist.xAxis = xAxis;
		if (xAxis.getModulToAlign() == null) {
			xAxis.setModulToAlign(this);
			axes.addAxis(xAxis);
		} 
		else {// scale this modul as the other
			if (xAxis.isHorizontal()) {
				cartesianAssist.setRightPadding(xAxis.getModulToAlign().getCartesianAssist().getRightPadding());
				cartesianAssist.setLeftPadding(xAxis.getModulToAlign().getCartesianAssist().getLeftPadding());
			} else {
				cartesianAssist.setBottomPadding(xAxis.getModulToAlign().getCartesianAssist().getBottomPadding());
				cartesianAssist.setTopPadding(xAxis.getModulToAlign().getCartesianAssist().getTopPadding());
			}
		}
	}

	public Axis getXAxis() {
		return cartesianAssist.xAxis;
	}

	public Axis getYAxis() {
		return cartesianAssist.yAxis;
	}

	public void setYAxis(Axis yAxis) {
		if(cartesianAssist.yAxis != null){
			axes.removeAxis(cartesianAssist.yAxis);
		}
		cartesianAssist.yAxis = yAxis;
		if (yAxis.getModulToAlign() == null) {
			yAxis.setModulToAlign(this);
			axes.addAxis(yAxis);
		}
		else {// scale this modul as the other
			if (yAxis.isHorizontal()) {
				cartesianAssist.setRightPadding(yAxis.getModulToAlign().getCartesianAssist().getRightPadding());
				cartesianAssist.setLeftPadding(yAxis.getModulToAlign().getCartesianAssist().getLeftPadding());
			}
			else {
				cartesianAssist.setBottomPadding(yAxis.getModulToAlign().getCartesianAssist().getBottomPadding());
				cartesianAssist.setTopPadding(yAxis.getModulToAlign().getCartesianAssist().getTopPadding());
			}
		}
	}

	public void addExtraAxis(Axis axis) {
		axis.setModulToAlign(this);
		axes.addAxis(axis);
		extraAxes.add(axis);
	}

	/**
	 * Aligns all extra axes to this modules parallel base axis 
	 */
	protected void alignExtraAxes(){
		for(Axis axis:extraAxes){
			Axis pairAxis = cartesianAssist.xAxis;
			if(AxisDirection.isPerpendicular(pairAxis, axis)){
				pairAxis = cartesianAssist.yAxis;
			}
			if(axis.getMax() == axis.getMin()){
				axis.setMax(pairAxis.getMax());
				axis.setMin(pairAxis.getMin());
			}
		}
	}

	public void removeExtraAxis(Axis axis) {
		extraAxes.remove(axis);
		axes.removeAxis(axis);
	}

	public boolean isAutoCalcPadding() {
		return autoCalcPadding;
	}

	/**
	 * @param autocalcPadding if true the module should be positioned manually
	 */
	public void setAutoCalcPadding(boolean autocalcPadding) {
		this.autoCalcPadding = autocalcPadding;
	}

	/**
	 * @param autoScaleViewport the autoScaleViewport to set
	 */
	public void setAutoScaleViewport(boolean autoScaleViewport) {
		this.autoScaleViewportHorizontal = autoScaleViewport;
		this.autoScaleViewportVertical = autoScaleViewport;
	}

	protected void updateCartesianAssist(int chartWidth, int chartHeight){
		cartesianAssist.setCharSize(chartWidth, chartHeight);
	}

	protected void updateCartesianAssist(){
		cartesianAssist.calculateParameters();
	}

	public LineProperties getBorder() {
		return border;
	}

	public void setBorder(LineProperties border) {
		this.border = border;
	}

	public Color getBackgroundColor() {
		return backgroundColor;
	}

	public void setBackgroundColor(Color backgroundColor) {
		this.backgroundColor = backgroundColor;
	}

	public int getMinTopPadding() {
		return minTopPadding;
	}

	public void setMinTopPadding(int minTopPadding) {
		this.minTopPadding = minTopPadding;
	}

	public int getMinLeftPadding() {
		return minLeftPadding;
	}

	public void setMinLeftPadding(int minLeftPadding) {
		this.minLeftPadding = minLeftPadding;
	}

	public int getMinBottomPadding() {
		return minBottomPadding;
	}

	public void setMinBottomPadding(int minBottomPadding) {
		this.minBottomPadding = minBottomPadding;
	}

	public int getMinRightPadding() {
		return minRightPadding;
	}

	public void setMinRightPadding(int minRightPadding) {
		this.minRightPadding = minRightPadding;
	}

	@Override
	public void setDisplayLegendEntries(boolean displayEntries) {
		this.displayLegendEntries = displayEntries;
	}

	@Override
	public boolean isDisplayLegendEntries() {
		return displayLegendEntries;
	}

	@Override
	public void setLegendEntries(TreeMap<String, Color> legendEntries) {
		this.legendEntries = legendEntries;
	}

	public boolean isAutoScaleViewportHorizontal() {
		return autoScaleViewportHorizontal;
	}

	public void setAutoScaleViewportHorizontal(boolean autoScaleViewportHorizontal) {
		this.autoScaleViewportHorizontal = autoScaleViewportHorizontal;
	}

	public boolean isAutoScaleViewportVertical() {
		return autoScaleViewportVertical;
	}

	public void setAutoScaleViewportVertical(boolean autoScaleViewportVertical) {
		this.autoScaleViewportVertical = autoScaleViewportVertical;
	}

	public CartesianAssist getCartesianAssist() {
		return cartesianAssist;
	}

	@Override
	public void setVisible(boolean visible) {
		this.visible = visible;
	}

	@Override
	public boolean isVisible() {
		return visible;
	}

	public double getTopPadding() {
		return cartesianAssist.padding[0];
	}	

	public double getRightPadding() {
		return cartesianAssist.padding[1];
	}

	public double getBottomPadding() {
		return cartesianAssist.padding[2];
	}

	public double getLeftPadding() {
		return cartesianAssist.padding[3];
	}

}
